/*
 * matrix.h
 *
 *  Created on: 03/06/2011
 *      Author: Ofer
 */

#include "matrix.h"
#include <assert.h>
#include "mtm.h"
#include <stdarg.h>
#include "mtmtest.h"
#define ASSERT_SINGULAR(matrix) \
	ASSERT_THROW(MatrixExceptions::Singular, matrix.inverse())
#define ASSERT_BAD_DIMENSIONS(expression) \
	ASSERT_THROW(MatrixExceptions::BadDimensions, expression)
#define ASSERT_INDEX_OUT_OF_RANGE(expression) \
	ASSERT_THROW(MatrixExceptions::IndexOutOfRange, expression)
#define ASSERT_DOUBLE_EQUAL(a,b) ASSERT_EQUALS(true,IS_DOUBLE_EQUAL(a, b))
#define ASSERT_DOUBLE_NOT_EQUAL(a,b) ASSERT_EQUALS(false,IS_DOUBLE_EQUAL(a, b))
#define ASSERT_NOT_EQUALS(expected,actual) do { \
		if(((expected) == (actual))) { \
			FAIL("values are equal"); \
		} \
	} while (0)


Matrix initMatrix( int rows, int columns, ... ){
	Matrix result( rows, columns );
	va_list vl;
	va_start( vl, columns );
	for( int r = 1; r <= rows; ++r ){
		for( int c = 1; c <= columns; ++c ){
			double cell = va_arg(vl,double);
			result(r, c) = cell;
		}
	}
	va_end( vl );
	return result;
}


bool standardCtorTest(){
	Matrix m1 = Matrix(5,1);
	Matrix m2 = Matrix(1,5);
	Matrix m3 = Matrix(1,1);
	ASSERT_BAD_DIMENSIONS(Matrix(0,1));
	ASSERT_BAD_DIMENSIONS(Matrix(1,0));
	ASSERT_BAD_DIMENSIONS(Matrix(0,0));
	ASSERT_BAD_DIMENSIONS(Matrix(-1,1));
	ASSERT_BAD_DIMENSIONS(Matrix(1,-1));
	ASSERT_BAD_DIMENSIONS(Matrix(-1,-1));
	return true;
}
bool rowsColumnsTest(){
	Matrix m1(2,3);
	ASSERT_EQUALS(2,m1.rows());
	ASSERT_EQUALS(3,m1.columns());
	Matrix m2(3,2);
	ASSERT_EQUALS(3,m2.rows());
	ASSERT_EQUALS(2,m2.columns());
	return true;
}

bool operatorCellGetTest(){
	Matrix m1 = initMatrix(3,4,
			1.0, 2.0, 3.0, 4.0,
			5.0, 6.0, 7.0, 8.0,
			9.0, 10.0, 11.0, 12.0);
	for (int r = 1; r <= m1.rows(); r++){
		for (int c = 1; c <= m1.columns(); c++){
	ASSERT_DOUBLE_EQUAL((r - 1)* 4 + c, m1(r,c));
		}
	}
	ASSERT_NO_THROW(m1(1,1) = 1000.0);
	ASSERT_DOUBLE_NOT_EQUAL(1.0, m1(1,1));
	ASSERT_DOUBLE_EQUAL(1000.0, m1(1,1));
	ASSERT_INDEX_OUT_OF_RANGE(m1(-1,-1));
	ASSERT_INDEX_OUT_OF_RANGE(m1(1,-1));
	ASSERT_INDEX_OUT_OF_RANGE(m1(-1,1));
	ASSERT_INDEX_OUT_OF_RANGE(m1(4,5));
	ASSERT_INDEX_OUT_OF_RANGE(m1(4,1));
	ASSERT_INDEX_OUT_OF_RANGE(m1(1,5));
	ASSERT_INDEX_OUT_OF_RANGE(m1(4,-1));
	ASSERT_INDEX_OUT_OF_RANGE(m1(-1,5));
	return true;
}

bool operatorCompareTest(){
	Matrix m1(1,1);
	Matrix m2(1,1);
	Matrix m3(1,2);
	ASSERT_BAD_DIMENSIONS(m1==m3);
	ASSERT_BAD_DIMENSIONS(m3==m1);
	ASSERT_BAD_DIMENSIONS(m2==m3);
	ASSERT_BAD_DIMENSIONS(m3==m2);
	ASSERT_NO_THROW(m1==m2);
	ASSERT_EQUALS(m1,m1);
	ASSERT_EQUALS(m3,m3);
	ASSERT_EQUALS(m1,m2);
	ASSERT_EQUALS(m2,m1);
	Matrix m4(1,2);
	ASSERT_EQUALS(m4,m3);
	ASSERT_EQUALS(m3,m4);
	Matrix m5 = Matrix::createUnit(1);
	ASSERT_NOT_EQUALS(m1,m5);
	ASSERT_NOT_EQUALS(m5,m1);
	return true;
}



bool copyCtorTest(){
	Matrix m1 = initMatrix(3,4,
			1.0, 2.0, 3.0, 4.0,
			5.0, 6.0, 7.0, 8.0,
			9.0, 10.0, 11.0, 11.0);
	Matrix m2 = Matrix(m1);
	ASSERT_EQUALS(m1,m2);
	ASSERT_NO_THROW(m1(1,1) = 0.0);
	ASSERT_NOT_EQUALS(m1,m2);
	ASSERT_DOUBLE_EQUAL(1.0, m2(1,1));
	return true;
}

bool unitCreateTest(){
	Matrix m1 = initMatrix(4,4,
			1.0, 0.0, 0.0, 0.0,
			0.0, 1.0, 0.0, 0.0,
			0.0, 0.0, 1.0, 0.0,
			0.0, 0.0, 0.0, 1.0);
	Matrix m2 = Matrix::createUnit(4);
	ASSERT_EQUALS(m1,m2);
	Matrix m3 = Matrix::createUnit(3);
	Matrix m4(3,3);
	for (int i = 1; i <= 3; i++){
		ASSERT_NO_THROW(m4(i,i) = 1.0);
		}
	ASSERT_EQUALS(m3,m4);
	ASSERT_BAD_DIMENSIONS(m3==m2);
	ASSERT_BAD_DIMENSIONS(m2==m3);
	ASSERT_BAD_DIMENSIONS(Matrix::createUnit(0));
	ASSERT_BAD_DIMENSIONS(Matrix::createUnit(-1));
	return true;
}






bool operatorPlusTest(){
	Matrix m1 = Matrix(4,4);
	Matrix m2 = Matrix::createUnit(4);
	Matrix m3 = Matrix(4,4);
	Matrix m4 = Matrix::createUnit(4);
	ASSERT_EQUALS(m2,m1+m2);
	ASSERT_EQUALS(m1,m3);
	ASSERT_EQUALS(m2,m4);
	ASSERT_EQUALS(m2,m2+=m1);
	ASSERT_EQUALS(m1,m3);
	ASSERT_EQUALS(m2,m4);
	ASSERT_EQUALS(m2,m1+=m2);
	ASSERT_EQUALS(m1,m4);
	ASSERT_EQUALS(m2,m4);
	ASSERT_EQUALS(m1,m2);
	Matrix m5 = initMatrix(4,3,
			1.0, 0.0, 0.0, 0.0,
			0.0, 1.0, 0.0, 0.0,
			0.0, 0.0, 1.0, 0.0);
	Matrix m6 = initMatrix(4,4,
			0.0, 0.0, 0.0, 2.2,
			0.0, 0.0, 0.0, 0.0,
			0.0, 0.0, 0.0, 0.0,
			0.0, 0.0, 0.0, 0.0);
	ASSERT_BAD_DIMENSIONS(m6+m5);
	ASSERT_BAD_DIMENSIONS(m5+m6);
	Matrix m7 = initMatrix(4,4,
			1.0, 0.0, 0.0, -2.2,
			0.0, 1.0, 0.0, 0.0,
			0.0, 0.0, 1.0, 0.0,
			0.0, 0.0, 0.0, 1.0);
	ASSERT_EQUALS(m1,m6+m7);
	ASSERT_EQUALS(m5*2,m5+m5);

	return true;
}
bool operatorMinuesTest(){
	Matrix m1 = Matrix(4,4);
	Matrix m2 = Matrix::createUnit(4);
	Matrix m3 = Matrix(4,4);
	Matrix m4 = Matrix::createUnit(4);
	ASSERT_EQUALS(m2,m2-m1);
	ASSERT_EQUALS(m1,m3);
	ASSERT_EQUALS(m2,m4);
	ASSERT_EQUALS(m2,m2-=m1);
	ASSERT_EQUALS(m1,m3);
	ASSERT_EQUALS(m2,m4);
	ASSERT_EQUALS(-m4,m1-=m2);
	ASSERT_EQUALS(-m4,m1);
	ASSERT_EQUALS(m4,m2);
	Matrix m5 = initMatrix(4,3,
			1.0, 0.0, 0.0, 0.0,
			0.0, 1.0, 0.0, 0.0,
			0.0, 0.0, 1.0, 0.0);
	Matrix m6 = initMatrix(4,4,
			0.0, 0.0, 0.0, 2.2,
			0.0, 0.0, 0.0, 0.0,
			0.0, 0.0, 0.0, 0.0,
			0.0, 0.0, 0.0, 0.0);
	ASSERT_BAD_DIMENSIONS(m6+m5);
	ASSERT_BAD_DIMENSIONS(m5+m6);
	Matrix m7 = initMatrix(4,4,
			1.0, 0.0, 0.0, 2.2,
			0.0, 1.0, 0.0, 0.0,
			0.0, 0.0, 1.0, 0.0,
			0.0, 0.0, 0.0, 1.0);
	ASSERT_EQUALS(-m4,m6-m7);
	ASSERT_EQUALS(m4,m7-m6);
	ASSERT_EQUALS(m5*2,m5-(-m5));
	return true;
}

bool operatorMultiplyTest(){
	Matrix m1 = initMatrix(3,3,
			 1.000, 2.000, 1.000,
			 1.000, 3.000, 4.000,
			 1.000, -2.000, 1.000);
	Matrix m2 = initMatrix(3,4,
			1.000, 2.000, 3.000, 4.000,
			1.000, 2.000, 4.000, 6.000,
			1.000, 2.000, 4.000, 8.000);
	Matrix m3 = initMatrix(3,4,
			4.000, 8.000, 15.000, 24.000,
			8.000, 16.000, 31.000, 54.000,
			0.000, 0.000, -1.000, 0.000);
	ASSERT_EQUALS(m3,m1*m2);
	ASSERT_BAD_DIMENSIONS(m2*m1);
	ASSERT_EQUALS(m3, m1*=m2);
	ASSERT_EQUALS(m3,m1);
	Matrix m4 = Matrix::createUnit(3);
	ASSERT_EQUALS(m1, m4*m1);
	Matrix m5(3,3);
	ASSERT_EQUALS(Matrix(3,4),m5*m1);
	ASSERT_EQUALS(m5,m4*m5);
	ASSERT_EQUALS(m5,m5*m4);
	ASSERT_EQUALS(m5,m4*=m5);
	ASSERT_EQUALS(m5,m4);
	Matrix A = initMatrix(3,3,
			3.44, 2.56, 1.24,
			2.56, 1.24, 3.44,
			1.24, 3.44, 2.56);
	Matrix B = initMatrix(3,3,
			1.11, 2.11, 3.11,
			4.11, 5.11, 6.11,
			7.11, 8.11, 9.11);
	Matrix rightResult = initMatrix(3,3,
			23.156400, 30.396400, 37.636400,
			32.396400, 39.636400, 46.876400,
			33.716400, 40.956400, 48.196400);
	ASSERT_EQUALS(true, A*B == rightResult);
	return true;
}


bool operatorNegationTest(){
	Matrix m1 = Matrix(4,3);
	ASSERT_EQUALS(m1, -m1);
	ASSERT_EQUALS(m1*(-1), -m1);
	Matrix m2 = Matrix::createUnit(3);
	Matrix m3 = -m2;
	for(int i = 1; i <= 3; i++){
		if (!IS_DOUBLE_EQUAL(m3(i,i),-1)){
			return false;
		}
	}
	m1(1,1) = 1000.23;
	Matrix m4 = -m1;
	-m4 = -(-m1);
	m1= -m1;
	-m4 = m1*(-1);
	if (!IS_DOUBLE_EQUAL(m4(1,1),-1000.23)){
		return false;
	}
	return true;
}

bool operatorMultiplyByScalarTest(){
	Matrix m1 = Matrix::createUnit(3);
	Matrix m2 = m1*2;
	Matrix m3 = initMatrix(3,3,
			 2.000, 0.000, 0.000,
			 0.000, 2.000, 0.000,
			 0.000, 0.000, 2.000);
	ASSERT_EQUALS(m2,m3);
	Matrix m4 = initMatrix(3,3,
			 1.000, 2.000, 1.000,
			 1.000, 3.000, 4.000,
			 1.000, -2.000, 1.000);
	ASSERT_EQUALS(m4*2,m3*m4);
	return true;
}
bool transposeTest(){
	ASSERT_EQUALS(Matrix::createUnit(3),(Matrix::createUnit(3)).transpose());
	Matrix m1 = initMatrix(3,3,
			 1.000, 2.000, 1.000,
			 1.000, 3.000, 4.000,
			 1.000, -2.000, 1.000);
	Matrix m2 = initMatrix(3,3,
			 1.000, 1.000, 1.000,
			 2.000, 3.000, -2.000,
			 1.000, 4.000, 1.000);
	ASSERT_EQUALS(m2,m1.transpose());
	ASSERT_EQUALS(m1,m2.transpose());
	Matrix m3 = initMatrix(3,4,
			1.000, 2.000, 3.000, 4.000,
			1.000, 2.000, 4.000, 6.000,
			1.000, 2.000, 4.000, 8.000);
	Matrix m4 = initMatrix(4,3,
			1.000, 1.000, 1.000,
			2.000, 2.000, 2.000,
			3.000, 4.000, 4.000,
			4.000, 6.000, 8.000);
	ASSERT_EQUALS(m3,m4.transpose());
	ASSERT_EQUALS(m4,m3.transpose());
	return true;
}

bool inverseTest(){
	Matrix m1 = initMatrix(3,3,
			1.000, 2.000, 0.000,
			-2.000, 0.000, 0.000,
			0.000, 0.000, 1.000);
	Matrix m2 = initMatrix(3,3,
			0.000, -0.500, 0.000,
			0.500, 0.250, 0.000,
			0.000, 0.000, 1.000);
	ASSERT_EQUALS(m1, m2.inverse());
	ASSERT_EQUALS(m2, m1.inverse());
	ASSERT_EQUALS(Matrix::createUnit(3), Matrix::createUnit(3).inverse());
	ASSERT_BAD_DIMENSIONS(Matrix(1,4).inverse());
	Matrix m3 = initMatrix(3,3,
			0.000, -0.500, 0.000,
			0.000, 0.250, 0.000,
			0.000, 0.000, 1.000);
	ASSERT_SINGULAR(m3.inverse());
	return true;
}

int main()
{
	RUN_TEST(standardCtorTest);
	RUN_TEST(rowsColumnsTest);
	RUN_TEST(operatorCellGetTest);
	RUN_TEST(operatorCompareTest);
	RUN_TEST(copyCtorTest);
	RUN_TEST(unitCreateTest);
	RUN_TEST(operatorPlusTest);
	RUN_TEST(operatorMinuesTest);
	RUN_TEST(operatorMultiplyTest);
	RUN_TEST(operatorNegationTest);
	RUN_TEST(operatorMultiplyByScalarTest);
	RUN_TEST(transposeTest);
	RUN_TEST(inverseTest);
	return 0;
}
